#include <bsl_err.h>
#include <bsl_sal.h>
#include <crypt_algid.h>
#include <crypt_eal_md.h>
#include <crypt_eal_provider.h>
#include <crypt_errno.h>
#include <crypt_md5.h>

#include <setjmp.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include <cmocka.h>

#include "helper.h"

#define GROUP_TEST_MAX 20

#define SDV_KAE_MD5_FUNC_TC002_N 7
#define SDV_KAE_MD5_FUNC_TC004_N 3
#define SDV_KAE_MD5_DUP_TC001_N  1

static const char SDV_KAE_MD5_FUNC_TC001_HEX[] = "d41d8cd98f00b204e9800998ecf8427e";

static const char *SDV_KAE_MD5_FUNC_TC002_HEX[SDV_KAE_MD5_FUNC_TC002_N][2] = {
    {"", "d41d8cd98f00b204e9800998ecf8427e"},
    {"61", "0cc175b9c0f1b6a831c399e269772661"},
    {"616263", "900150983cd24fb0d6963f7d28e17f72"},
    {"6d65737361676520646967657374", "f96b697d7cb7938d525a2f31aaf161d0"},
    {"6162636465666768696a6b6c6d6e6f707172737475767778797a", "c3fcd3d76192e4007dfb496cca67e13b"},
    {"4142434445464748494a4b4c4d4e4f505152535455565758595a6162636465666768696a6b6c6d6e6f70717273747"
     "5767778797a30313233343536373839",
     "d174ab98d277d9f5a5611c2c9f419d9f"},
    {"313233343536373839303132333435363738393031323334353637383930313233343536373839303132333435363"
     "7383930313233343536373839303132333435363738393031323334353637383930",
     "57edf4a22be3c955ac49da2e2107b67a"},
};

static const char *SDV_KAE_MD5_FUNC_TC004_HEX[SDV_KAE_MD5_FUNC_TC004_N + 1] = {
    "313233343536373839",
    "30313233343536373839303132333435363738393031323334353637383930313233343536",
    "37383930313233343536373839303132333435363738393031323334353637383930",
    "57edf4a22be3c955ac49da2e2107b67a",
};

static const char *SDV_KAE_MD5_DUP_TC001_HEX[SDV_KAE_MD5_FUNC_TC002_N][2] = {
    {"6d65737361676520646967657374", "f96b697d7cb7938d525a2f31aaf161d0"},
};

static CRYPT_EAL_LibCtx *CreateLoadLibCtx()
{
    CRYPT_EAL_LibCtx *libctx = CRYPT_EAL_LibCtxNew();
    assert_non_null(libctx);
    assert_int_equal(CRYPT_EAL_ProviderSetLoadPath(libctx, "../../build"), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_ProviderLoad(libctx, BSL_SAL_LIB_FMT_LIBSO, "kae_provider", NULL, NULL), CRYPT_SUCCESS);
    return libctx;
}

static void SDV_KAE_MD5_PROVIDER_LOAD(void **state)
{
    (void)state;
    CRYPT_EAL_LibCtx *libctx = CreateLoadLibCtx();
    CRYPT_EAL_LibCtxFree(libctx);
}

static void SDV_KAE_MD5_API_TC001(void **state)
{
    (void)state;

    uint8_t data[100];
    uint32_t dataLen = sizeof(data);
    uint8_t output[CRYPT_MD5_DIGESTSIZE + 1];
    uint32_t outputLen = CRYPT_MD5_DIGESTSIZE;

    CRYPT_EAL_LibCtx *libctx = CreateLoadLibCtx();
    CRYPT_EAL_MdCTX *md5Ctx = CRYPT_EAL_ProviderMdNewCtx(libctx, CRYPT_MD_MD5, "provider=kaev1");
    assert_non_null(md5Ctx);

    assert_int_equal(CRYPT_EAL_MdUpdate(md5Ctx, data, dataLen), CRYPT_EAL_ERR_STATE);
    assert_int_equal(CRYPT_EAL_MdFinal(md5Ctx, output, &outputLen), CRYPT_EAL_ERR_STATE);

    assert_int_equal(CRYPT_EAL_MdInit(md5Ctx), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdUpdate(md5Ctx, NULL, dataLen), CRYPT_NULL_INPUT);
    assert_int_equal(CRYPT_EAL_MdUpdate(md5Ctx, data, dataLen), CRYPT_SUCCESS);

    assert_int_equal(CRYPT_EAL_MdFinal(NULL, output, &outputLen), CRYPT_NULL_INPUT);
    assert_int_equal(CRYPT_EAL_MdFinal(md5Ctx, NULL, &outputLen), CRYPT_NULL_INPUT);
    assert_int_equal(CRYPT_EAL_MdFinal(md5Ctx, output, NULL), CRYPT_NULL_INPUT);

    outputLen = CRYPT_MD5_DIGESTSIZE - 1;
    assert_int_equal(CRYPT_EAL_MdFinal(md5Ctx, output, &outputLen), CRYPT_EAL_BUFF_LEN_NOT_ENOUGH);
    outputLen = CRYPT_MD5_DIGESTSIZE;
    assert_int_equal(CRYPT_EAL_MdFinal(md5Ctx, output, &outputLen), CRYPT_SUCCESS);

    assert_int_equal(CRYPT_EAL_MdDeinit(md5Ctx), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdInit(md5Ctx), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdUpdate(md5Ctx, data, dataLen), CRYPT_SUCCESS);
    outputLen = CRYPT_MD5_DIGESTSIZE + 1;
    assert_int_equal(CRYPT_EAL_MdFinal(md5Ctx, output, &outputLen), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdFinal(md5Ctx, output, &outputLen), CRYPT_EAL_ERR_STATE);
    assert_int_equal(CRYPT_EAL_MdUpdate(md5Ctx, data, dataLen), CRYPT_EAL_ERR_STATE);

    CRYPT_EAL_MdFreeCtx(md5Ctx);
    CRYPT_EAL_LibCtxFree(libctx);
}

static void SDV_KAE_MD5_FUNC_TC001(void **state)
{
    const char *expect = *((const char **)state);
    Hex *hex = NewHex();
    assert_int_equal(ConvertHex(expect, hex), 0);

    uint8_t output[CRYPT_MD5_DIGESTSIZE];
    uint32_t outLen = CRYPT_MD5_DIGESTSIZE;

    CRYPT_EAL_LibCtx *libctx = CreateLoadLibCtx();
    CRYPT_EAL_MdCTX *md5Ctx = CRYPT_EAL_ProviderMdNewCtx(libctx, CRYPT_MD_MD5, "provider=kaev1");
    assert_non_null(md5Ctx);
    assert_int_equal(CRYPT_EAL_MdInit(md5Ctx), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdFinal(md5Ctx, output, &outLen), CRYPT_SUCCESS);
    assert_int_equal(outLen, CRYPT_MD5_DIGESTSIZE);

    assert_int_equal(outLen, hex->len);
    assert_memory_equal(output, hex->x, outLen);

    FreeHex(hex);
    CRYPT_EAL_MdFreeCtx(md5Ctx);
    CRYPT_EAL_LibCtxFree(libctx);
}

static void SDV_KAE_MD5_FUNC_TC002(void **state)
{
    uint8_t output[CRYPT_MD5_DIGESTSIZE];
    uint32_t outLen = CRYPT_MD5_DIGESTSIZE;

    const char **test = (const char **)*state;
    const char *in = test[0];
    const char *ou = test[1];

    Hex *msg = NewHex();
    Hex *out = NewHex();

    assert_int_equal(ConvertHex(in, msg), 0);
    assert_int_equal(ConvertHex(ou, out), 0);

    CRYPT_EAL_LibCtx *libctx = CreateLoadLibCtx();
    CRYPT_EAL_MdCTX *md5Ctx = CRYPT_EAL_ProviderMdNewCtx(libctx, CRYPT_MD_MD5, "provider=kaev1");
    assert_non_null(md5Ctx);
    assert_int_equal(CRYPT_EAL_MdInit(md5Ctx), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdUpdate(md5Ctx, msg->x, msg->len), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdFinal(md5Ctx, output, &outLen), CRYPT_SUCCESS);
    assert_int_equal(outLen, CRYPT_MD5_DIGESTSIZE);

    assert_int_equal(outLen, out->len);
    assert_memory_equal(output, out->x, outLen);

    FreeHex(msg);
    FreeHex(out);

    CRYPT_EAL_MdFreeCtx(md5Ctx);
    CRYPT_EAL_LibCtxFree(libctx);
}

static void SDV_KAE_MD5_FUNC_TC003(void **state)
{
    (void)state;

    uint8_t input[5050];
    uint32_t inLenTotal = 0;
    uint32_t inLenBase;
    uint8_t outA[CRYPT_MD5_DIGESTSIZE];
    uint8_t outB[CRYPT_MD5_DIGESTSIZE];
    uint32_t outLen = CRYPT_MD5_DIGESTSIZE;

    CRYPT_EAL_LibCtx *libctx = CreateLoadLibCtx();
    CRYPT_EAL_MdCTX *ctxA = CRYPT_EAL_ProviderMdNewCtx(libctx, CRYPT_MD_MD5, "provider=kaev1");
    CRYPT_EAL_MdCTX *ctxB = CRYPT_EAL_ProviderMdNewCtx(libctx, CRYPT_MD_MD5, "provider=kaev1");

    assert_non_null(ctxA);
    assert_non_null(ctxB);

    assert_int_equal(CRYPT_EAL_MdInit(ctxA), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdInit(ctxB), CRYPT_SUCCESS);

    for (inLenBase = 1; inLenBase <= 100; inLenBase++) {
        assert_int_equal(CRYPT_EAL_MdUpdate(ctxA, input + inLenTotal, inLenBase), CRYPT_SUCCESS);
        inLenTotal += inLenBase;
    }

    assert_int_equal(CRYPT_EAL_MdFinal(ctxA, outA, &outLen), CRYPT_SUCCESS);
    assert_int_equal(outLen, CRYPT_MD5_DIGESTSIZE);

    assert_int_equal(CRYPT_EAL_MdUpdate(ctxB, input, inLenTotal), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdFinal(ctxB, outB, &outLen), CRYPT_SUCCESS);
    assert_int_equal(outLen, CRYPT_MD5_DIGESTSIZE);

    assert_memory_equal(outA, outB, outLen);

    CRYPT_EAL_MdFreeCtx(ctxA);
    CRYPT_EAL_MdFreeCtx(ctxB);
    CRYPT_EAL_LibCtxFree(libctx);
}

static void SDV_KAE_MD5_FUNC_TC004(void **state)
{
    uint8_t output[CRYPT_MD5_DIGESTSIZE];
    uint32_t outLen = CRYPT_MD5_DIGESTSIZE;

    const char **test = (const char **)*state;
    Hex *hex[SDV_KAE_MD5_FUNC_TC004_N + 1];

    for (size_t i = 0; i <= SDV_KAE_MD5_FUNC_TC004_N; i++) {
        hex[i] = NewHex();
        assert_int_equal(ConvertHex(test[i], hex[i]), 0);
    }

    CRYPT_EAL_LibCtx *libctx = CreateLoadLibCtx();
    CRYPT_EAL_MdCTX *md5Ctx = CRYPT_EAL_ProviderMdNewCtx(libctx, CRYPT_MD_MD5, "provider=kaev1");
    assert_non_null(md5Ctx);
    assert_int_equal(CRYPT_EAL_MdInit(md5Ctx), CRYPT_SUCCESS);

    for (size_t i = 0; i < SDV_KAE_MD5_FUNC_TC004_N; i++) {
        assert_int_equal(CRYPT_EAL_MdUpdate(md5Ctx, hex[i]->x, hex[i]->len), CRYPT_SUCCESS);
    }

    assert_int_equal(CRYPT_EAL_MdFinal(md5Ctx, output, &outLen), CRYPT_SUCCESS);
    assert_int_equal(outLen, CRYPT_MD5_DIGESTSIZE);

    assert_memory_equal(output, hex[SDV_KAE_MD5_FUNC_TC004_N]->x, outLen);

    for (size_t i = 0; i <= SDV_KAE_MD5_FUNC_TC004_N; i++) {
        FreeHex(hex[i]);
    }

    CRYPT_EAL_MdFreeCtx(md5Ctx);
    CRYPT_EAL_LibCtxFree(libctx);
}

static void SDV_KAE_MD5_DUP_TC001(void **state)
{
    uint8_t output[CRYPT_MD5_DIGESTSIZE];
    uint32_t outLen = CRYPT_MD5_DIGESTSIZE;

    const char **test = (const char **)*state;
    const char *in = test[0];
    const char *ou = test[1];

    Hex *msg = NewHex();
    Hex *out = NewHex();

    assert_int_equal(ConvertHex(in, msg), 0);
    assert_int_equal(ConvertHex(ou, out), 0);

    CRYPT_EAL_LibCtx *libctx = CreateLoadLibCtx();
    CRYPT_EAL_MdCTX *md5Ctx = CRYPT_EAL_ProviderMdNewCtx(libctx, CRYPT_MD_MD5, "provider=kaev1");
    assert_non_null(md5Ctx);

    CRYPT_EAL_MdCTX *dupCtx = NULL;
    dupCtx = CRYPT_EAL_MdDupCtx(NULL);
    assert_null(dupCtx);

    dupCtx = CRYPT_EAL_MdDupCtx(md5Ctx);
    assert_non_null(dupCtx);
    assert_int_equal(CRYPT_EAL_MdInit(dupCtx), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdUpdate(dupCtx, msg->x, msg->len), CRYPT_SUCCESS);
    assert_int_equal(CRYPT_EAL_MdFinal(dupCtx, output, &outLen), CRYPT_SUCCESS);

    assert_int_equal(outLen, out->len);
    assert_memory_equal(output, out->x, outLen);

    FreeHex(msg);
    FreeHex(out);

    CRYPT_EAL_MdFreeCtx(md5Ctx);
    CRYPT_EAL_MdFreeCtx(dupCtx);
    CRYPT_EAL_LibCtxFree(libctx);
}

int main(void)
{
    struct CMUnitTest tests[GROUP_TEST_MAX] = {NULL};
    int testCnt = 0;

    tests[testCnt++] = (struct CMUnitTest)cmocka_unit_test(SDV_KAE_MD5_PROVIDER_LOAD);
    tests[testCnt++] = (struct CMUnitTest)cmocka_unit_test(SDV_KAE_MD5_API_TC001);
    tests[testCnt++] =
        (struct CMUnitTest)cmocka_unit_test_prestate(SDV_KAE_MD5_FUNC_TC001, (void **)&SDV_KAE_MD5_FUNC_TC001_HEX);

    for (size_t i = 0; i < SDV_KAE_MD5_FUNC_TC002_N; i++) {
        tests[testCnt++] =
            (struct CMUnitTest)cmocka_unit_test_prestate(SDV_KAE_MD5_FUNC_TC002, &SDV_KAE_MD5_FUNC_TC002_HEX[i]);
    }

    tests[testCnt++] = (struct CMUnitTest)cmocka_unit_test(SDV_KAE_MD5_FUNC_TC003);
    tests[testCnt++] =
        (struct CMUnitTest)cmocka_unit_test_prestate(SDV_KAE_MD5_FUNC_TC004, &SDV_KAE_MD5_FUNC_TC004_HEX);
    tests[testCnt++] = (struct CMUnitTest)cmocka_unit_test_prestate(SDV_KAE_MD5_DUP_TC001, &SDV_KAE_MD5_DUP_TC001_HEX);

    cmocka_run_group_tests(tests, NULL, NULL);
    return 0;
}
